package com.bite.job.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bite.common.core.constants.CacheConstants;
import com.bite.common.core.constants.Constants;
import com.bite.common.redis.service.RedisService;
import com.bite.job.domain.exam.Exam;
import com.bite.job.domain.message.Message;
import com.bite.job.domain.message.MessageText;
import com.bite.job.domain.message.vo.MessageTextVO;
import com.bite.job.domain.user.UserExam;
import com.bite.job.domain.user.UserScore;
import com.bite.job.mapper.exam.ExamMapper;
import com.bite.job.mapper.message.MessageTextMapper;
import com.bite.job.mapper.user.UserExamMapper;
import com.bite.job.mapper.user.UserSubmitMapper;
import com.bite.job.service.IMessageService;
import com.bite.job.service.IMessageTextService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.management.MemoryManagerMXBean;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
public class ExamXxlJob {
    @Autowired
    public ExamMapper examMapper;
    @Autowired
    public RedisService redisService;
    @Autowired
    public UserSubmitMapper userSubmitMapper;
    @Autowired
    public MessageTextMapper messageTextMapper;
    @Autowired
    private IMessageTextService messageTextService;
    @Autowired
    private IMessageService messageService;
    @Autowired
    private UserExamMapper userExamMapper;

    @XxlJob("examListOrganizeHandler")
    public void examListOrganizeHandler() {

        //统计哪些竞赛应该存入未完赛的列表中，哪些竞赛应该存入历史竞赛列表中，统计出来之后，再存入对应缓存中
        //他不需要像是缓存处理那个统计完赛还是未完赛，这个无需考虑，全处理就好
        log.info("************ examListOrganizeHandler ******************");
        List<Exam> unFinishList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                //gt大于当前时间
                .gt(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));
        refreshCache(unFinishList,CacheConstants.EXAM_UNFINISHED_LIST );
        List<Exam> historyList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                //le是小于等于
                .le(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));
        refreshCache(historyList,CacheConstants.EXAM_HISTORY_LIST);
    }

    @XxlJob("examResultHandler")
    public void  examResultHandler(){
        LocalDateTime now=LocalDateTime.now();
        //前一天
        LocalDateTime minusDatetime=now.minusDays(1);
        //.ge(Exam::getEndTime, minusDatetime):
        //添加一个大于或等于条件，要求 endTime 字段的值大于或等于 minusDatetime。
        //.le(Exam::getEndTime, now):
        //添加一个小于或等于条件，要求 endTime 字段的值小于或等于 now
        List<Exam>examList= examMapper.selectList(new LambdaQueryWrapper<Exam>()
                       .select(Exam::getExamId,Exam::getTitle)
                        .eq(Exam::getStatus,Constants.TRUE)
                .ge(Exam::getEndTime,minusDatetime)
                .le(Exam::getEndTime,now));
       if(CollectionUtil.isEmpty(examList)){
           return;
       }
       //查找出来竞赛来，对竞赛进行统计，每个分我们都存储了，我们可以通过SQL计算,时间范围内，所有竞赛的对应的id组成的集合
       Set<Long> examIdSet=examList.stream().map(Exam::getExamId).collect(Collectors.toSet());
       //通过user-submit
        List<UserScore>userScoreList=userSubmitMapper.selectUserScoreList(examIdSet); //查找整体的排名情况
        //group by 拿到map结构，竞赛id,和竞赛id对应的排名情况,5个竞赛，分成5堆，每一堆，代表对应的排名数据
        Map<Long,List<UserScore>>userScoreMap=userScoreList.stream().collect(Collectors.groupingBy(UserScore::getExamId));
        //生成排名消息
        createMessage(examList,userScoreMap);
    }
//尽量批量操作
    private void createMessage(List<Exam> examList,Map<Long,List<UserScore>> userScoreMap) {
        List<MessageText> messageTextList=new ArrayList<>();
        List<Message> messageList=new ArrayList<>();
        for(Exam exam:examList){
            Long examId=exam.getExamId();
            List<UserScore> userScoreList=userScoreMap.get(examId);
            int totalUser=userScoreList.size();
            int examRank=1;
            for(UserScore userScore:userScoreList){
                String msgTitle=exam.getTitle()+"--------排名情况";
                String msgContent="您所参与的竞赛:"+exam.getTitle()+", 本次参与竞赛一共"+totalUser+"人, " +
                        "您排名第"+examRank+"名:";
                userScore.setExamRank(examRank);
                //分贝存入MessageText表
                MessageText messageText=new MessageText();
                messageText.setMessageTitle(msgTitle);
                messageText.setMessageContent(msgContent);
                //定时任务在跑的时候，没有用户这一个说法，没有用户的概念，ThreadLocal没有数据，这里面
                //循环插入，效率不是很高，需要很多次调用，和第三方组件交互多，考虑一下批量操作(用一个List临时存储这个数据)
                messageText.setCreateBy(Constants.SYSTEM_USER_ID);
                messageTextList.add(messageText);
                //和message表
                Message message=new Message();
                message.setSendId(Constants.SYSTEM_USER_ID);
                message.setCreateBy(Constants.SYSTEM_USER_ID);
                message.setRecId(userScore.getUserId());
                messageList.add(message);
                examRank++;
                //这里我原本思想也是把它应该用一个列表存起来，然后直接循环插入，但是当前版本mb(有可能是我的mysql设置有一些问题)不支持循环插入
                userExamMapper.updateUserScoreAndRank(userScore);
            }
            redisService.rightPushAll(getExamRankListKey(examId),userScoreList);
        }
        //批量插入，    数据库的存储
        messageTextService.batchInsert(messageTextList);
        //Message
        Map<String,MessageTextVO> messageTextVOMap=new HashMap<>(); //这个是用来批量插入redis的
        for(int i=0;i<messageTextList.size();i++){
            MessageText messageText=messageTextList.get(i);
            MessageTextVO messageTextVO=new MessageTextVO();
            BeanUtil.copyProperties(messageText,messageTextVO);
            String msgDetailKey= getDetailKey(messageText.getTextId());
            //批量插入redis也是一样
            messageTextVOMap.put(msgDetailKey,messageTextVO);
            Message message=messageList.get(i);
            message.setTextId(messageText.getTextId());
        }
        messageService.batchInsert(messageList);

        //redis存储   redis存储的结构注意: u:m:l:用户id value textId
       Map<Long,List<Message>>userMsgMap= messageList.stream().collect(Collectors.groupingBy(Message::getRecId));
       Iterator<Map.Entry<Long,List<Message>>>iterator=userMsgMap.entrySet().iterator();
       while(iterator.hasNext()){
           Map.Entry<Long,List<Message>>entry=iterator.next();
           Long recId=entry.getKey();
           //拼接redis存储的key
           String userMsgListKey=getUserMsgListKey(recId);
           //存入redis
           List<Long>userMsgTextIdList=  entry.getValue().stream().map(Message::getTextId).collect(Collectors.toList());
           redisService.rightPushAll(userMsgListKey,userMsgTextIdList);
       }
       //redis批量操作
        redisService.multiSet(messageTextVOMap);
    }

    //查询的是用户竞赛列表
        public void refreshCache(List<Exam>examList,String examListKey){
        if (CollectionUtil.isEmpty(examList)) {
            return;
        }
        Map<String, Exam> examMap = new HashMap<>();
        List<Long> examIdList = new ArrayList<>();
        for (Exam exam : examList) {
            examMap.put(getDetailKey(exam.getExamId()), exam);
            examIdList.add(exam.getExamId());
        }
        redisService.multiSet(examMap);  //刷新详情缓存
        //有可能他有数据，直接调用了，有可能list里面有数据，但是数据不正确，所以需要给他清除（)
        redisService.deleteObject(examListKey);//此处我认为是一种类似于延迟双删的感觉
        redisService.rightPushAll(examListKey, examIdList);
    }

    private String getExamRankListKey(Long examId){
        return  CacheConstants.EXAM_RANK_LIST+examId;
    }
    private String getDetailKey(Long examId) {
        return CacheConstants.EXAM_DETAIL + examId;
    }
    //拼接redis的key
    private String getUserMsgListKey(Long userId){
        return CacheConstants.USER_MESSAGE_LIST+userId;
    }
    private String getMsgDetailKey(Long textId){
        return CacheConstants.MESSAGE_DETAIL+textId;
    }
}
